package main

import (
	"fmt"
	"reflect"
)

/*
使用反射来遍历结构体的字段，调用结构体的方法，并获取结构体标签的值
*/
type Monster struct {
	Name  string `json:"name"`
	Age   int    `json:"monster_age"`
	Score float32
	Sex   string
}

func (s Monster) Print() {
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}
func (s Monster) GetSum(n1, n2 int) int {
	return n1 + n2
}
func (s Monster) Set(name string, age int, score float32, sex string) {
	s.Name = name
	s.Age = age
	s.Score = score
	s.Sex = sex
}
func TestStruct(a interface{}) {
	typ := reflect.TypeOf(a)
	val := reflect.ValueOf(a)
	kd := val.Kind()
	//如果传入的不是struct，就退出
	if kd != reflect.Struct {
		fmt.Println("expect struct")
		return
	}
	//获取到该结构体有几个字段
	/*
		func (v Value) NumField() int
		返回v持有的结构体类型值的字段数，如果v的Kind不是Struct会panic
	*/
	num := val.NumField()
	fmt.Printf("struct has %d fields\n", num)
	//变量结构体的所有字段
	for i := 0; i < num; i++ {
		/*
			func (v Value) Field(i int) Value
			返回结构体的第i个字段（的Value封装）。如果v的Kind不是Struct或i出界会panic
		*/
		fmt.Printf("Field %d: 值为=%v\n", i, val.Field(i))
		//获取到struct标签，注意需要通过reflect.Type来获取tag标签的值
		/*
			Field(i int) StructField
			返回索引序列指定的嵌套字段的类型，
			等价于用索引中每个值链式调用本方法，如非结构体将会panic
			func (tag StructTag) Get(key string) string
			Get方法返回标签字符串中键key对应的值。如果标签中没有该键，会返回""。如果标签不符合标准格式，Get的返回值是不确定的。
		*/
		tagVal := typ.Field(i).Tag.Get("json")
		//如果该字段有tag标签就显示，否则不显示
		if tagVal != " " {
			fmt.Printf("Field %d: tag为=%v\n", i, tagVal)
		}
	}
	//获取到结构体有多少个方法
	/*
		func (v Value) NumMethod() int
		返回v持有值的方法集的方法数目。
	*/
	numOfMethod := val.NumMethod()
	fmt.Printf("struct has %d methods\n", numOfMethod)

	//方法的排序默认是按照函数名的排序(ASCII码)
	/*
		func (v Value) Method(i int) Value
		返回v持有值类型的第i个方法的已绑定（到v的持有值的）状态的函数形式的Value封装。
		返回值调用Call方法时不应包含接收者；返回值持有的函数总是使用v的持有者作为接收者（即第一个参数）。
		如果i出界，或者v的持有值是接口类型的零值（nil），会panic。

		func (v Value) Call(in []Value) []Value
		Call方法使用输入的参数in调用v持有的函数。
		例如，如果len(in) == 3，v.Call(in)代表调用v(in[0], in[1], in[2])（其中Value值表示其持有值）。
		如果v的Kind不是Func会panic。它返回函数所有输出结果的Value封装的切片。
		和go代码一样，每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。
		如果v持有值是可变参数函数，Call方法会自行创建一个代表可变参数的切片，将对应可变参数的值都拷贝到里面。
	*/
	val.Method(1).Call(nil) //获取到第二个方法，调用它
	//调用结构体的第1个方法Method(0)
	var params []reflect.Value //声明了 []reflect.Value
	params = append(params, reflect.ValueOf(10))
	params = append(params, reflect.ValueOf(40))
	res := val.Method(0).Call(params) //传入的参数是[]reflect.Value
	fmt.Println("res=", res[0].Int()) //返回结果，返回的结果是[]reflect.Value

}
func main() {
	var a Monster = Monster{
		Name:  "黄鼠狼精",
		Age:   20,
		Score: 30.8,
	}
	//将Monster实例传递给TestStruct函数
	TestStruct(a)
}
